/**************************************************************************/ /**
 * @file     load_mv.c
 * @brief    播放 avi / wav 音视频
 * @version  V3.5
 * @date     2022.09.15
******************************************************************************/
#include <string.h>
#include "jfif_parser.h"
#include "avi_parser.h"
#include "wav_parser.h"
#include "ff.h"

#include "load_mv.h"

#include "irq_handler.h"
#include "bsp_init.h"
#include "mem_sdram.h"
#include "posix_fsm.h"
#include "log.h"

/* 应用场景说明 :
 * 1、播放 avi 视频(带音轨)
 * 2、播放 avi 视频(不带音轨)
 * 3、播放 wav 音频
 * 1 与 3 互斥, 2 与 3 理论可同时进行, 但综合考虑实际应用场景与 MCU 处理能力(例如在PC上拼接视频与音轨可合成 1), 故此处将 2 与 3 设为互斥, 暂未添加对该需求的支持.
 */
//---------------------------------------------本地宏----------------------------------------------//
//#define MV_DEBUG_ENABLE

#ifdef MV_DEBUG_ENABLE

#define MV_LOG(...) 		LOG_INFO(__VA_ARGS__)

#else

#define MV_LOG(...)			

#endif

//音频数据缓冲区不可放到 SDRAM 内，因 DMA -> DAC 会与 LCD_DMA 竞争 SDRAM 带宽，
//导致 LCD 的 DCLK 在数据发送过程中会中断停止，实测表现为屏幕显示闪烁/晃动, 故堆不可设置在 SDRAM .

#define PCM_BUFSIZE 		(2048) //此处直接定义成2048，比较好的方法应该是根据 wav/avi 文件头解析出的大小在堆中申请空间

//--------------------------------------------类型声明----------------------------------------------//
// ping pong buffer control structure
typedef struct
{
	int16_t *buf;
	volatile uint8_t ping : 1;
	volatile uint8_t pong : 1;
} ppbuf_t; //乒乓双缓冲

//音视频播放状态
typedef enum
{
	fsm_state_idle = 0, //空闲态

	fsm_state_run_avi, //运行态-avi
	fsm_state_run_wav  //运行态-wav
} fsm_state_t;

//音视频事件
typedef enum
{
	fsm_event_idle = 0, //空闲挂起(特权级)

	fsm_event_parser_avi, //解析
	fsm_event_parser_wav,

	fsm_event_decode, //解码

	fsm_event_close //关闭
} fsm_event_t;

//-------------------------------------------本地函数声明---------------------------------------------//
static int cb_parser_avi(void *);
static int cb_decode_avi(void *);
static int cb_close_avi(void *);

static int cb_parser_wav(void *);
static int cb_decode_wav(void *);
static int cb_close_wav(void *);

//-------------------------------------------全局变量定义---------------------------------------------//


//-------------------------------------------本地变量定义---------------------------------------------//
static const fsm_handle_t FSM_Event_Table[] = {
	/* 初态				  触发事件				  动作回调		  次态 */
//	{fsm_state_idle		, fsm_event_idle		, NULL			, fsm_state_idle},

	{fsm_state_idle		, fsm_event_parser_avi	, cb_parser_avi	, fsm_state_run_avi},
	{fsm_state_idle		, fsm_event_parser_wav	, cb_parser_wav	, fsm_state_run_wav},
	
	{fsm_state_run_avi	, fsm_event_decode		, cb_decode_avi	, fsm_state_run_avi},
	{fsm_state_run_avi	, fsm_event_close		, cb_close_avi	, fsm_state_idle},

	{fsm_state_run_wav	, fsm_event_decode		, cb_decode_wav	, fsm_state_run_wav},
	{fsm_state_run_wav	, fsm_event_close		, cb_close_wav	, fsm_state_idle},
};

static fsm_t FSM = {
	.table = (fsm_handle_t *)FSM_Event_Table,
	.table_num = sizeof(FSM_Event_Table) / sizeof(FSM_Event_Table[0])
};

static fsm_event_t Event_ID = fsm_event_idle;

//音频
static int16_t Audio_PCM_Buff[PCM_BUFSIZE * 2];
static ppbuf_t Audio_Control = {&Audio_PCM_Buff[0], 0, 1};

//视频
static jpeg_outset_t Video_OutSet; // Video流JPEG解码输出设定

/* 播放媒体时的相关信息 */
static FIL File_Handle;								  //文件句柄
static uint32_t Restart_Stream_ID = 0;				  //流起始数据类型
static uint32_t Restart_Offset = 0;					  //流起始数据偏移量
static uint32_t Restart_StreamSize = 0;				  //流起始的数据大小
static volatile uint32_t Video_Loop_Nums = 0;		  //当前视频已循环播放次数
static volatile uint32_t Video_Loop_Max = 0xFFFFFFFF; //视频循环播放次数上限( uint32_t )

//--------------------------------------------函数定义----------------------------------------------//
/**
 * @brief 	音视频任务处理
 * @param 	/
 * @retval	0 - 运行		> 0 - 挂起		< 0 - 错误
 * @note	轮询
 */
int mv_task_handler(void)
{
	if (Event_ID == fsm_event_idle)
		return 1;
	
	return ((FSM_RT_OK == fsm_event_exec(&FSM, Event_ID, NULL)) ? 0 : -1);
}

/**
 * @brief 	结束播放
 * @param 	/
 * @retval	/
 * @note	/
 */
void mv_task_over(void)
{
	fsm_result res;
	int now_state = 0;
	res = fsm_state_get(&FSM, &now_state);
	if (res != FSM_RT_OK)
	{
		MV_LOG("[fsm_state_get] res = [%d]\r\n", res);
		return ;
	}
	
	switch (now_state)
	{
		case (fsm_state_idle) : 
		break;
		
		case (fsm_state_run_avi) : //break; //continue
		case (fsm_state_run_wav) : 
			res = fsm_event_exec(&FSM, fsm_event_close, NULL);
		break;
		
		default : break;
	}
	
	if (res == FSM_RT_OK)
	{
		Event_ID = fsm_event_idle;
	}
}

/**
 * @brief 	启动播放
 * @param 	file_path : 源文件绝对路径
 * @retval	/
 * @note	/
 */
void mv_task_init(const char *file_path)
{
	mv_task_over(); //正在运行中打断, 需要先关闭

	//以文件后缀名区分
	if (NULL != strstr(file_path, ".avi"))
	{
		if (fsm_event_exec(&FSM, fsm_event_parser_avi, (void *)file_path) == FSM_RT_OK)
		{
			Event_ID = fsm_event_decode;
		}
	}
	else if (NULL != strstr(file_path, ".wav"))
	{
		if (fsm_event_exec(&FSM, fsm_event_parser_wav, (void *)file_path) == FSM_RT_OK)
		{
			Event_ID = fsm_event_decode;
		}
	}
	else // (unknown) other file format
	{
		Event_ID = fsm_event_idle;
	}
}

/**
 * @brief 	暂停播放
 * @param 	/
 * @retval	/
 * @note	仅实现音频
 */
void mv_task_pause(void)
{
	fsm_result res;
	int now_state = 0;
	res = fsm_state_get(&FSM, &now_state);
	if (res == FSM_RT_OK)
	{
		if (now_state != fsm_state_idle)
		{
			TIMR_Halt(DAC_TIMR);
		}
	}
}

/**
 * @brief 	恢复播放
 * @param 	/
 * @retval	/
 * @note	仅实现音频
 */
void mv_task_resume(void)
{
	fsm_result res;
	int now_state = 0;
	res = fsm_state_get(&FSM, &now_state);
	if (res == FSM_RT_OK)
	{
		if (now_state != fsm_state_idle)
		{
			TIMR_Resume(DAC_TIMR);
		}
	}
}

/**
 * @brief 	设置视频循环播放次数
 * @param 	times	: 当前播放次数
 * 			max		: 上限
 * @retval	/
 * @note	/
 */
void mv_task_set_loop(const uint32_t times, const uint32_t max)
{
	Video_Loop_Nums = times;
	Video_Loop_Max = max;
}

/**
 * @brief 	获取视频循环播放次数
 * @param 	times	: 当前播放次数
 * 			max		: 上限
 * @retval	/
 * @note	/
 */
void mv_task_get_loop(uint32_t *times, uint32_t *max)
{
	if (NULL != times)
		*times = Video_Loop_Nums;

	if (NULL != max)
		*max = Video_Loop_Max;
}

/**
 * @brief 	设置图层显示区域
 * @param 	lcd_layer	: 图层
 * 			xs / ys		: 起始位置
 * 			xe / ye		: 终止位置
 * @retval	/
 * @note	视频源大小一般不可改变, 若该函数在视频播放时调用, 可使新设置的播放区域大小自动适配源的大小(依赖 Avix 结构体)
 */
void lcd_set_post(lcd_layer_t lcd_layer, uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
	//参数校验
	if (xs >= (LCD_HDOT - 1) || ys >= (LCD_VDOT - 1))
	{
		MV_LOG("[assert] xs = [%d], ys = [%d]\r\n", xs, ys);
		return; //错误抛出 assert
	}

	//检查 Avix 是否有效
	if (Avix.Width > 0 && Avix.Height > 0)
	{ //图层大小自适应视频源
		xe = ((xs + Avix.Width - 1) > (LCD_HDOT - 1)) ? (LCD_HDOT - 1) : (xs + Avix.Width - 1);
		ye = ((ys + Avix.Height - 1) > (LCD_VDOT - 1)) ? (LCD_VDOT - 1) : (ys + Avix.Height - 1);
	}
	else //按预期设定值设置
	{
		if (xe > (LCD_HDOT - 1) || ye > (LCD_VDOT - 1) || xs >= xe || ys >= ye)
		{
			MV_LOG("[No Avix] x = [%d ~ %d], y = [%d ~ %d]\r\n", xs, xe, ys, ye);
			return; //错误抛出
		}
		xe = (xe > LCD_HDOT - 1) ? (LCD_HDOT - 1) : (xe - 1);
		ye = (ye > LCD_VDOT - 1) ? (LCD_VDOT - 1) : (ye - 1);
	}

	if (((xe - xs) & 0x1) == 0) //每行像素点数必须为偶数, 而 0 表示 1 个像素点, 故 差 必须为奇数
	{
		if (xe < LCD_HDOT - 1) //变更奇偶性-优先改变 xe
			xe += 1;
		else
			xs -= 1;
	}

	LCD->L[lcd_layer].LLEN = (xe - xs);

	LCD->L[lcd_layer].WHP = (xs << LCD_WHP_STA_Pos) |
							(xe << LCD_WHP_STP_Pos);

	LCD->L[lcd_layer].WVP = (ys << LCD_WVP_STA_Pos) |
							(ye << LCD_WVP_STP_Pos);

	LCD->L[lcd_layer].LCR |= (1 << LCD_LCR_EN_Pos);

	LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
}

/**
 * @brief 	视频中心跟随触摸移动
 * @param 	x / y : 触控中心
 * @retval	/
 * @note	默认为区域视频, 故调整移动图层 2 位置
 */
void video_center_with_tp(uint16_t x, uint16_t y)
{
	//获取视频中心
	uint16_t video_center_x = (Avix.Width >> 1) + (Avix.Width & 0b1);
	uint16_t video_center_y = (Avix.Height >> 1) + (Avix.Height & 0b1);
		
	//边界碰撞处理
	uint16_t xs = (x < video_center_x) ? /*video_center_x*/0 : x - video_center_x;						// left
	xs = (x > LCD_HDOT - video_center_x) ? (LCD_HDOT - video_center_x * 2) : xs;
	//uint16_t xe = (x > LCD_HDOT - video_center_x) ? (LCD_HDOT - video_center_x * 2) : x;// right
	
	uint16_t ys = (y < video_center_y) ? /*video_center_y*/0 : y - video_center_y;						// up
	ys = (y > LCD_VDOT - video_center_y) ? (LCD_VDOT - video_center_y * 2) : ys;
	//uint16_t ye = (y > LCD_VDOT - video_center_y) ? (LCD_VDOT - video_center_y * 2) : y;// down

	lcd_set_post(LAYER_2_VIDEO, xs, ys, 0, 0);
}

/**
 * @brief 	设置图层透明度
 * @param 	lcd_layer	: 图层
 * 			alpha		: 透明度
 * @retval	/
 * @note	图层 x 的 constant alpha，用于 blending。
			Blending 公式：
			BC = (C * CONSTA + Cs * (255 - CONSTA))/255
			BC: blended color
			C : current layer color
			Cs: subjacent layers blended color
			有如下几种 blending 情况：
			图层 1 和图层 2 都使能时：先将图层 1 和背景颜色进行 blending，然后将图层 2 和
			blending 后的图层进行 blending。
			图层 1 或图层 2 仅有一个图层使能时：将被使能的图层和背景颜色进行 blending。
			两个图层都不使能时：仅显示背景色。
 */
void lcd_set_alpha(lcd_layer_t lcd_layer, uint8_t alpha)
{
	LCD->L[lcd_layer].LCR &= ~(0xFF << LCD_LCR_ALPHA_Pos);
	LCD->L[lcd_layer].LCR |= (alpha << LCD_LCR_ALPHA_Pos);

	LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
}

/**
 * @brief 	设置图层色键控
 * @param 	lcd_layer			: 图层
 * 			color_key_enable	: 色键控使能
 * 			red / green	/ blue	: rgb
 * @retval	/
 * @note	LCD_FORMAT 优先于 Color Key 配置
 */
void lcd_set_color_key(lcd_layer_t lcd_layer, bool color_key_enable, uint8_t red, uint8_t green, uint8_t blue)
{
	if (color_key_enable)
	{
		LCD->L[lcd_layer].LCR |= (1 << LCD_LCR_CKEN_Pos);

		// LCD_FORMAT 优先于 Color Key 配置
#if (LCD_FMT == LCD_RGB565)
		red >>= 3;
		green >>= 2;
		blue >>= 3;
		LCD->L[lcd_layer].CK = (red << 11) | (green << 5) | (blue << 0);

#elif (LCD_FMT == LCD_RGB888)
		LCD->L[lcd_layer].CK = (red << 16) | (green << 8) | (blue << 0);

#endif // LCD_FORMAT
	}
	else
	{
		LCD->L[lcd_layer].LCR &= ~(1 << LCD_LCR_CKEN_Pos);

		LCD->L[lcd_layer].CK = 0;
	}

	LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
}

/**
 * @brief 	设置视频解码输出格式
 * @param 	format			: 解码输出格式，可取值JPEG_OUT_YUV、JPEG_OUT_YUVsp、JPEG_OUT_XRGB888、...详见 swm341_jpeg.h
 * 			rgb565_dither	: RGB565 dithering enable
 * @retval	/
 * @note	仅 format 为 JPEG_OUT_RGB565 时, dither 有效
 */
void video_outset_format(uint8_t format, uint8_t rgb565_dither)
{
	Video_OutSet.format = format;
	Video_OutSet.dither = (JPEG_OUT_RGB565 == format) ? rgb565_dither : 0; // RGB565 dithering enable
}

//-----------------------------------------------本地函数定义------------------------------------------------//
/**
 * @brief 	关闭 avi 视频
 * @param 	data : 事件参数
 * @retval	next_state
 * @note	/
 */
static int cb_close_avi(void *data)
{
	if (Avix.AudioBufSize > 0) //视频有音轨
	{
		dac_deinit();
	}
	TIMR_Stop(AVI_TIMR);
	f_close(&File_Handle);

	uint32_t free_ptr = LCD->L[LAYER_1_GUI].ADDR;
	if (((LCD->L[LAYER_2_VIDEO].LCR) >> LCD_LCR_EN_Pos) != 0) //判断第 2 图层是否被优先使用(与 parser avi() 策略对应)
	{
		LCD->L[LAYER_2_VIDEO].LCR &= ~(1 << LCD_LCR_EN_Pos); //配置 LCD 第 2 图层 失能
		LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos);				 // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
		free_ptr = LCD->L[LAYER_2_VIDEO].ADDR;
	}

	MV_LOG("[sdram_free] [Video_OutSet_Ptr] = 0x[%x], ", free_ptr);
	sdram_free((void *)free_ptr);
	free_ptr = NULL;
	MV_LOG("sdram_free_size = [%d]\r\n", sdram_get_idle_size());

	memset(&Avix, 0, sizeof(Avix));

	MV_LOG("stream loop nums = [%d], Max_times = [%d]\r\n", Video_Loop_Nums, Video_Loop_Max);
	Video_Loop_Nums = 0; //清零循环播放计数值, 对于每个视频都是从零开始

	return fsm_state_idle;
}

/**
 * @brief 	关闭 wav 音频
 * @param 	data : 自定义参数传递
 * @retval	next_state
 * @note	/
 */
static int cb_close_wav(void *data)
{
	dac_deinit();
	f_close(&File_Handle);
	return fsm_state_idle;
}

/**
 * @brief 	解析 avi 视频头
 * @param 	data : avi file path
 * @retval	next_state
 * @note	/
 */
static int cb_parser_avi(void *data)
{
	const char *const file_path = data;
	FRESULT f_res = FR_OK;
	AVISTATUS avi_res = AVI_OK;
	uint32_t read_len = 0;
	void *video_frame_ptr = JPEG_Decoder_Buff;

#if (LCD_FMT == LCD_RGB565)
	uint32_t video_frame_size = LCD_HDOT * LCD_VDOT * sizeof(uint16_t);
#elif (LCD_FMT == LCD_RGB888)
	uint32_t video_frame_size = LCD_HDOT * LCD_VDOT * sizeof(uint32_t);
#endif // LCD_FORMAT

// 480*480 个别视频第一帧视频 Avix 头信息在大于一帧长度后面,故此处多读一些, 以保证 avi_init() 正确解析 Avix 头
#if (LCD_HDOT == 480 && LCD_VDOT == 480)
	video_frame_size = sizeof(JPEG_Decoder_Buff) / 4 * 5;
	video_frame_ptr = sdram_malloc(video_frame_size); //分配临时读取视频第一帧的缓存, 稍后解析完 Avix 头便释放

	MV_LOG("[%s] file [sdram_malloc] [video_frame_ptr] = 0x[%p], size = [%d], sdram_free_size = [%d]\r\n",
		   file_path, video_frame_ptr, video_frame_size, sdram_get_idle_size());
	if (NULL == video_frame_ptr)
	{
		return fsm_state_idle;
	}
#endif //(LCD_HDOT == 480 && LCD_VDOT == 480)

	//打开文件
	f_res = f_open(&File_Handle, file_path, FA_READ | FA_OPEN_EXISTING);
	if (FR_OK != f_res)
	{
		MV_LOG("[%s] file [f_open] result = [%s]\r\n", file_path, Debug_FR_Table[f_res]);
		return fsm_state_idle;
	}
	// 读取 AVI 文件头
	f_res = f_read(&File_Handle, video_frame_ptr, video_frame_size, &read_len);
	if (FR_OK != f_res || 0 == read_len)
	{
		MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", file_path, Debug_FR_Table[f_res], read_len);
		goto error_parser_avi;
	}
	// 对 AVI 文件头进行解析
	avi_res = avi_init((uint8_t *)video_frame_ptr, read_len);
	if (AVI_OK != avi_res)
	{
		MV_LOG("[%s] file [avi_init] result error[%d], read_len = [%d]\r\n", file_path, avi_res, read_len);
		goto error_parser_avi;
	}
	// 寻找 movi ID
	Restart_Offset = avi_srarch_id((uint8_t *)video_frame_ptr, read_len, "movi");
	if (0 == Restart_Offset)
	{
		MV_LOG("[%s] file [avi_srarch_id] result error, read_len = [%d]\r\n", file_path, read_len);
		goto error_parser_avi;
	}
	// 获取第一帧信息
	avi_res = avi_get_streaminfo((uint8_t *)(video_frame_ptr + Restart_Offset + 4));
	if (AVI_OK != avi_res)
	{
		MV_LOG("[%s] file [avi_get_streaminfo] result error[%d]\r\n", file_path, avi_res);
		goto error_parser_avi;
	}
	//跳过标志ID,读地址偏移到流数据开始处
	f_lseek(&File_Handle, Restart_Offset + 12);

	//保存播放时的起始信息，便于重新播放
	Restart_Stream_ID = Avix.StreamID;
	Restart_StreamSize = Avix.StreamSize;
	MV_LOG("fps:%d.\r\n", 1000 / (Avix.SecPerFrame / 1000));

#if (LCD_HDOT == 480 && LCD_VDOT == 480)
	sdram_free(video_frame_ptr); //释放临时读取视频第一帧的缓存
	MV_LOG("[%s] file [sdram_free] [video_frame_ptr] = 0x[%p], size = [%d], sdram_free_size = [%d]\r\n",
		   file_path, video_frame_ptr, video_frame_size, sdram_get_idle_size());
	video_frame_ptr = NULL;
#endif //(LCD_HDOT == 480 && LCD_VDOT == 480)

	//配置解码选项
#if (LCD_FMT == LCD_RGB565)
	Video_OutSet.dither = 0; // RGB565 dithering enable
	Video_OutSet.format = JPEG_OUT_RGB565;
	video_frame_size = Avix.Width * Avix.Height * sizeof(uint16_t);
#elif (LCD_FMT == LCD_RGB888)
	Video_OutSet.dither = 0;
	Video_OutSet.format = JPEG_OUT_XRGB888;
	video_frame_size = Avix.Width * Avix.Height * sizeof(uint32_t);
#endif // LCD_FORMAT

	//配置视频帧解码输出区域
	void *mem_ptr = sdram_malloc(video_frame_size);
	MV_LOG("[%s] file [sdram_malloc] [Video_OutSet_Ptr] = 0x[%p], size = [%d], sdram_free_size = [%d]\r\n",
		   file_path, mem_ptr, video_frame_size, sdram_get_idle_size());
	if (NULL == mem_ptr)
	{
		goto error_parser_avi;
	}
	Video_OutSet.RGBAddr = (uint32_t)mem_ptr;
	memset(mem_ptr, 0x00, video_frame_size); //初始为黑色

	//设定播放图层策略:
	//播放全屏视频时默认使用 LAYER_1_GUI 以避免大面积图层混合运算而导致绘图效率降低
	if (LCD_HDOT <= Avix.Width && LCD_VDOT <= Avix.Height)
	{
		LCD->L[LAYER_1_GUI].ADDR = (uint32_t)mem_ptr;
		LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
	}
	else //播放局部区域视频使用 LAYER_2_VIDEO 进行混合叠图应用
	{
		//默认起始位置
		uint16_t x = 0, y = 0;
		LCD_LayerInitStructure LCD_LayerInStruct;
		LCD_LayerInStruct.Alpha = 0xFF; //默认不透明
		LCD_LayerInStruct.HStart = (x >= LCD_HDOT - 1) ? (LCD_HDOT - Avix.Width - 1) : x;
		LCD_LayerInStruct.VStart = (y >= LCD_VDOT - 1) ? (LCD_VDOT - Avix.Height - 1) : y;
		// Stop 必须大于 Start, 且 HStart - HStop必须为奇数
		LCD_LayerInStruct.HStop = ((LCD_LayerInStruct.HStart + Avix.Width) > LCD_HDOT) ? (LCD_HDOT - 1) : (LCD_LayerInStruct.HStart + Avix.Width - 1);
		LCD_LayerInStruct.VStop = ((LCD_LayerInStruct.VStart + Avix.Height) > LCD_VDOT) ? (LCD_VDOT - 1) : (LCD_LayerInStruct.VStart + Avix.Height - 1);
		LCD_LayerInStruct.DataSource = (uint32_t)mem_ptr; //数据源地址
		LCD_LayerInit(LCD, LAYER_2_VIDEO, &LCD_LayerInStruct);

		//LCD->L[LAYER_2_VIDEO].LCR &= ~(1 << LCD_LCR_EN_Pos); 	//位置还未正确设定,先不显示
		//LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); 				// 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
	}
	//--------------------------------------------------------------视频音轨处理---------------------------------------------------------------------------------//
	if (Avix.AudioBufSize > 0) //视频源有音轨
	{
		// DAC_TIMR 用于控制按音频采样率播放音频，每秒中断 Avix.SampleRate 次
		TIMR_Init(DAC_TIMR, TIMR_MODE_TIMER, CyclesPerUs, 1000000 / Avix.SampleRate, 0);
		dac_dma_init((uint16_t *)&Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE], Avix.AudioBufSize / sizeof(uint16_t));

		if (AVI_AUDS_FLAG == Avix.StreamID) //音频帧在前
		{
			f_res = f_read(&File_Handle, &(Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE]), Avix.StreamSize, &read_len);
			if (FR_OK != f_res || 0 == Avix.StreamSize || 0 == read_len) //判断当前帧有无音轨数据
			{
				MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", file_path, Debug_FR_Table[f_res], read_len);
				goto error_parser_avi;
			}
			// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
			for (uint32_t i = 0; i < Avix.StreamSize / sizeof(uint16_t); ++i)
			{
				Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE + i] += 32768;
			}
			//音频流 ID 信息
			uint8_t stream_buff[8];
			f_res = f_read(&File_Handle, stream_buff, 8, &read_len);
			if (FR_OK != f_res || 0 == read_len)
			{
				MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", file_path, Debug_FR_Table[f_res], read_len);
				goto error_parser_avi;
			}
			//读取下一帧 流标志
			avi_res = avi_get_streaminfo(stream_buff);
			if (AVI_OK != avi_res)
			{
				MV_LOG("[%s] file [avi_get_streaminfo] result error[%d]\r\n", file_path, avi_res);
				goto error_parser_avi;
			}

			IRQ_Flag_Audio = FREE; // DAC_DMA 传输状态
			TIMR_Start(DAC_TIMR);
		}
		else //视频帧在前
		{
			IRQ_Flag_Audio = BUSY; // DAC_DMA 传输状态
			TIMR_Start(DAC_TIMR);
		}
	}
	IRQ_Flag_Frame = BUSY; // AVI帧传输状态

	// AVI_TIMR 用于控制帧率，每 Avix.SecPerFrame us 中断一次
	TIMR_Init(AVI_TIMR, TIMR_MODE_TIMER, CyclesPerUs, Avix.SecPerFrame, 1);
	TIMR_Start(AVI_TIMR);

	return fsm_state_run_avi;

error_parser_avi:
	f_close(&File_Handle);
	return fsm_state_idle;
}

/**
 * @brief 	解析 wav 音频头
 * @param 	data : wav file path
 * @retval	next_state
 * @note	/
 */
static int cb_parser_wav(void *data)
{
	const char *const file_path = data;
	FRESULT f_res = FR_OK;
	uint32_t read_len = 0;
	uint8_t wav_head_buf[512] = {0};

	//打开文件
	f_res = f_open(&File_Handle, file_path, FA_READ | FA_OPEN_EXISTING);
	if (FR_OK != f_res)
	{
		MV_LOG("[%s] file [f_open] result = [%s]\r\n", file_path, Debug_FR_Table[f_res]);
		return fsm_state_idle;
	}
	// 读取 wav 文件头
	f_res = f_read(&File_Handle, wav_head_buf, sizeof(wav_head_buf), &read_len); //读取 512 字节头数据
	if (FR_OK != f_res || 0 == read_len)
	{
		MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", file_path, Debug_FR_Table[f_res], read_len);
		goto error_parser_wav;
	}
	// 对文件头进行解析
	if (0 != wav_init((uint8_t *)wav_head_buf, read_len))
	{
		MV_LOG("[%s] file [wav_init] result error, read_len = [%d]\r\n", file_path, read_len);
		goto error_parser_wav;
	}
	//跳过文件头
	f_lseek(&File_Handle, Wavx.datastart);

	//读取第一个音轨
	f_res = f_read(&File_Handle, &Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE], PCM_BUFSIZE * sizeof(uint16_t), &read_len);
	if (FR_OK != f_res || 0 == read_len)
	{
		MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", file_path, Debug_FR_Table[f_res], read_len);
		goto error_parser_wav;
	}
	/*不够数据了,补充 0
	if (read_len < PCM_BUFSIZE * sizeof(uint16_t))
	{
		for (uint32_t i = read_len / sizeof(uint16_t); i < (PCM_BUFSIZE - (read_len / sizeof(uint16_t))); ++i)
			Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE + i] = 0;
	}
	*/
	// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
	for (uint32_t i = 0; i < read_len / sizeof(uint16_t); ++i)
	{
		Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE + i] += 32768;
	}
	IRQ_Flag_Audio = BUSY; // dac dma 传输状态

	// DAC_TIMR 用于控制按音频采样率播放音频，每秒中断 Wavx.samplerate 次
	TIMR_Init(DAC_TIMR, TIMR_MODE_TIMER, CyclesPerUs, 1000000 / Wavx.samplerate, 0);
	dac_dma_init((uint16_t *)&Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE], read_len / sizeof(uint16_t));
	TIMR_Start(DAC_TIMR);

	return fsm_state_run_wav;

error_parser_wav:
	f_close(&File_Handle);
	return fsm_state_idle;
}

/**
 * @brief 	解码 avi 视频播放
 * @param 	data : 事件参数
 * @retval	next_state
 * @note	/
 */
static int cb_decode_avi(void *data)
{
	FRESULT f_res = FR_OK;
	uint32_t read_len = 0;

	if (Avix.StreamID == AVI_VIDS_FLAG) //视频图像
	{
		if (IRQ_Flag_Frame == FREE) //等待帧时间到达
		{
			IRQ_Flag_Frame = BUSY; //在 TIMR 中断里面设置为 FREE

			//如果准备解码下一帧图片时，上一帧图片还没有解码完成，会在此等待，
			//最好不要出现这种情况，如果出现这种情况，表示MCU处理不过来，应降低播放视频的帧率
			while (0 != JPEG_DecodeBusy(JPEG))
				; //等待 JPEG 硬件解码器空闲可用

			//读入整帧+下一数据流ID信息
			f_res = f_read(&File_Handle, JPEG_Decoder_Buff, Avix.StreamSize + 8, &read_len);
			if (0 != Avix.StreamSize) //当前帧有图像数据,则进行解码显示
			{
				if (JFIF_RES_OK == jfif_parse((const uint8_t *)JPEG_Decoder_Buff, Avix.StreamSize, &JFIF_Info))
					JPEG_Decode(JPEG, &JFIF_Info, &Video_OutSet);
			}
			//读取下一帧 流标志
			if (AVI_OK != avi_get_streaminfo(JPEG_Decoder_Buff + Avix.StreamSize))
			{
				//累计循环播放次数
				if (++Video_Loop_Nums < Video_Loop_Max)
				{
					//回到播放第一帧的状态, 避免频繁开关文件句柄带来的效率损失
					Avix.StreamID = Restart_Stream_ID;
					Avix.StreamSize = Restart_StreamSize;
					f_lseek(&File_Handle, Restart_Offset + 12);
				}
				else
				{
					MV_LOG("video frame over!\r\n");
					while (FREE != IRQ_Flag_Audio)
						; //阻塞等待最后一个音轨播放完毕
					return cb_close_avi(data);
				}
			}
		}
	}
	else if (IRQ_Flag_Audio == FREE) //等待上一轨音频播放完成//Avix.StreamID == AVI_AUDS_FLAG
	{
		IRQ_Flag_Audio = BUSY; //在 DMA 中断内置 FREE

		//读入音轨
		f_res = f_read(&File_Handle, &Audio_Control.buf[Audio_Control.pong * PCM_BUFSIZE], Avix.StreamSize, &read_len);
		if (FR_OK != f_res || 0 == Avix.StreamSize || 0 == read_len) //判断当前帧有无音轨数据
		{
			// MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", File_Path, Debug_FR_Table[f_res], read_len);
			while (FREE != IRQ_Flag_Audio)
				; //阻塞等待最后一个音轨播放完毕
			return cb_close_avi(data);
		}
		// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
		for (uint32_t i = 0; i < Avix.StreamSize / sizeof(uint16_t); ++i)
		{
			Audio_Control.buf[Audio_Control.pong * PCM_BUFSIZE + i] += 32768;
		}
		// 由于上述转换过程中，直接播放会有卡顿，切换 ping 和 pong 会有改善
		Audio_Control.ping = Audio_Control.ping ^ Audio_Control.pong;
		Audio_Control.pong = Audio_Control.pong ^ Audio_Control.ping;
		Audio_Control.ping = Audio_Control.ping ^ Audio_Control.pong;
		//切换 DAC_DMA 源
		DMA->CH[DAC_DMA_CHN].SRC = (uint32_t)&Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE];
		DMA->CH[DAC_DMA_CHN].CR |= ((read_len / sizeof(uint16_t) - 1) << DMA_CR_LEN_Pos);
		//音频流ID信息
		uint8_t stream_buff[8];
		f_res = f_read(&File_Handle, stream_buff, 8, &read_len);
		//读取下一帧 流标志
		if (AVI_OK != avi_get_streaminfo(stream_buff))
		{
			//累计循环播放次数
			if (++Video_Loop_Nums < Video_Loop_Max)
			{
				//回到播放第一帧的状态, 避免频繁开关文件句柄带来的效率损失
				Avix.StreamID = Restart_Stream_ID;
				Avix.StreamSize = Restart_StreamSize;
				f_lseek(&File_Handle, Restart_Offset + 12);
			}
			else
			{
				MV_LOG("video audio over!\r\n");
				while (FREE != IRQ_Flag_Audio)
					; //阻塞等待最后一个音轨播放完毕
				return cb_close_avi(data);
			}
		}
	}
	return fsm_state_run_avi;
}

/**
 * @brief 	解码 wav 音频播放
 * @param 	data : 事件参数
 * @retval	next_state
 * @note	/
 */
static int cb_decode_wav(void *data)
{
	FRESULT f_res = FR_OK;
	uint32_t read_len = 0;
	
	if (IRQ_Flag_Audio == FREE) //等待上一轨音频播放完成
	{
		IRQ_Flag_Audio = BUSY; //在 DAC_DMA_CHN 中断内置 FREE

		//读入音轨
		f_res = f_read(&File_Handle, &Audio_Control.buf[Audio_Control.pong * PCM_BUFSIZE], PCM_BUFSIZE * sizeof(uint16_t), &read_len);
		if (FR_OK != f_res || 0 == read_len)
		{
			// MV_LOG("[%s] file [f_read] result = [%s], read_len = [%d]\r\n", File_Path, Debug_FR_Table[f_res], read_len);
			while (FREE != IRQ_Flag_Audio)
				; //阻塞等待最后一个音轨播放完毕
			return cb_close_wav(data);
		}
		/*不够数据了,补充 0
		if (read_len < PCM_BUFSIZE * sizeof(uint16_t))
		{
			for (uint32_t i = read_len / sizeof(uint16_t); i < PCM_BUFSIZE - read_len / sizeof(uint16_t); ++i)
				Audio_Control.buf[Audio_Control.pong * PCM_BUFSIZE + i] = 0;
		}
		*/
		// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
		for (uint32_t i = 0; i < read_len / sizeof(uint16_t); ++i)
		{
			Audio_Control.buf[Audio_Control.pong * PCM_BUFSIZE + i] += 32768;
		}
		// 由于上述转换过程中，直接播放会有卡顿，切换 ping 和 pong 会有改善
		Audio_Control.ping = Audio_Control.ping ^ Audio_Control.pong;
		Audio_Control.pong = Audio_Control.pong ^ Audio_Control.ping;
		Audio_Control.ping = Audio_Control.ping ^ Audio_Control.pong;
		//切换 DAC_DMA 源
		DMA->CH[DAC_DMA_CHN].SRC = (uint32_t)&Audio_Control.buf[Audio_Control.ping * PCM_BUFSIZE];
		DMA->CH[DAC_DMA_CHN].CR |= ((read_len / sizeof(uint16_t) - 1) << DMA_CR_LEN_Pos);
	}
	return fsm_state_run_wav;
}
